#include "stdafx.h"
#ifdef __BORLANDC__
    #pragma hdrstop
#endif

#include "main.h"
#include "exception.h"
#include "filelist.h"
#include "filethread.h"
#include "ini.h"
#include "log.h"
#include <wx/log.h>
#include <wx/wfstream.h>

char *err_str = "[error]";

wxString sLogo(_T("File backup utility")),
	sVersion(_T("3.3.1"));
wxCriticalSection csOut;
wxCriticalSection csFrame;
wxCriticalSection csThread;
wxIcon *icon1 = NULL;
wxIcon *icon2 = NULL;

wxString
Ini::src(_T("from"))
	,Ini::srcl(_T("froml"))
	,Ini::dst(_T("to"))
	,Ini::sync(_T("sync"))
	,Ini::verbose(_T("v"))
	,Ini::help(_T("h"))
	,Ini::ifiles(_T("if"))
	,Ini::ifilesl(_T("ifl"))
	,Ini::efiles(_T("ef"))
	,Ini::efilesl(_T("efl"))
	,Ini::edirs(_T("ed"))
	,Ini::edirsl(_T("edl"))
	,Ini::dele(_T("delete"))
	,Ini::restore(_T("restore"))
	,Ini::backup(_T("backup"))
	,Ini::compress(_T("compress"))
	,Ini::logstr(_T("log"))
	,Ini::lappend(_T("lappend"))
	,Ini::csizestr(_T("csize"))
	,Ini::ifpath(_T("ifpath"))
	;


static const wxCmdLineEntryDesc cmdLineDesc[] =
	{
		{ wxCMD_LINE_OPTION, Ini::sync, NULL, ("operation type (backup | restore)"), wxCMD_LINE_VAL_STRING, wxCMD_LINE_OPTION_MANDATORY | wxCMD_LINE_NEEDS_SEPARATOR },
		{ wxCMD_LINE_OPTION, Ini::dst, NULL, ("destination dir"), wxCMD_LINE_VAL_STRING, wxCMD_LINE_OPTION_MANDATORY | wxCMD_LINE_NEEDS_SEPARATOR },
		{ wxCMD_LINE_OPTION, Ini::src, NULL, ("source dir"), wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL | wxCMD_LINE_NEEDS_SEPARATOR },
		{ wxCMD_LINE_OPTION, Ini::srcl, NULL, ("file with source dir list"), wxCMD_LINE_VAL_STRING, wxCMD_LINE_NEEDS_SEPARATOR },
		{ wxCMD_LINE_OPTION, Ini::ifiles, NULL, ("include file list (regular expression)"), wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL |  wxCMD_LINE_NEEDS_SEPARATOR },
		{ wxCMD_LINE_OPTION, Ini::ifilesl, NULL, ("file with include file list (regular expression)"), wxCMD_LINE_VAL_STRING, wxCMD_LINE_NEEDS_SEPARATOR },
		{ wxCMD_LINE_OPTION, Ini::edirs, NULL, ("exclude dir list (regular expression)"), wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL | wxCMD_LINE_NEEDS_SEPARATOR },
		{ wxCMD_LINE_OPTION, Ini::edirsl, NULL, ("file with exclude dir list (regular expression)"), wxCMD_LINE_VAL_STRING, wxCMD_LINE_NEEDS_SEPARATOR },
		{ wxCMD_LINE_OPTION, Ini::efiles, NULL, ("exclude file list (regular expression)"), wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL | wxCMD_LINE_NEEDS_SEPARATOR },
		{ wxCMD_LINE_OPTION, Ini::efilesl, NULL, ("file with exclude file list (regular expression)"), wxCMD_LINE_VAL_STRING, wxCMD_LINE_NEEDS_SEPARATOR },
		{ wxCMD_LINE_OPTION, Ini::logstr, NULL, ("write output to file"), wxCMD_LINE_VAL_STRING, wxCMD_LINE_NEEDS_SEPARATOR },
		{ wxCMD_LINE_SWITCH, Ini::dele, NULL, ("delete files from destination dir (for backup operation type only)") },
		{ wxCMD_LINE_SWITCH, Ini::ifpath, NULL, ("include [from] path in [to]")},
		{ wxCMD_LINE_SWITCH, Ini::lappend, NULL, ("append log to exisiting log file")},
		{ wxCMD_LINE_SWITCH, Ini::verbose, NULL, ("be verbose") },
		{ wxCMD_LINE_SWITCH, Ini::help, NULL, ("display help"), wxCMD_LINE_VAL_NONE, wxCMD_LINE_OPTION_HELP },
  
		{ wxCMD_LINE_NONE }
	};


void checkOneOrOther(wxCmdLineParser* clp, const wxString &one, const wxString &other, bool req)
{
	wxString s;
	if( clp->Found(one, &s) && clp->Found(other, &s) )
	{
		s = _T("");
		s << _T("Only \'") << one << _T("\' or \'") << other << _T("\' should be specified.");
		throw Exception(s);
	}
	if( req && !clp->Found(one, &s) && !clp->Found(other, &s) )
	{
		s = _T("");
		s << _T("\'") << one << _T("\' or \'") << other << _T("\' should be specified.");
		throw Exception(s);
	}
}

#if !defined(FINAL)
#include <fcntl.h>
#include <io.h>
#include <sys/stat.h>
void writeFile(wxString message)
{
	//int file = open("d:\\wxThread.log", O_RDWR | O_CREAT | O_APPEND, S_IREAD | S_IWRITE);
	//if( file >= 0 )
	//{
	//	write(file, "\n", 1);
	//	write(file, message.c_str(), message.Length());
	//	close(file);
	//}
}
void myOut(const wxChar *string)
{
	csOut.Enter();

	Log::Debug(string);
	writeFile(string);
	csOut.Leave();
}

void myOut(char *string)
{
	wxWCharBuffer wb = wxConvUTF8.cMB2WC(string);

	csOut.Enter();

	Log::Debug(wb.data());
	writeFile(wb.data());
	csOut.Leave();
	
}
#endif // !FINAL

/* MainApp */

IMPLEMENT_APP(MainApp)

BEGIN_EVENT_TABLE(MainApp, wxApp)
	EVT_QUERY_END_SESSION(MainApp::OnQueryEndSession)
END_EVENT_TABLE()

MainApp::MainApp()
	: wxApp()
{
	myOut("in MainApp::MainApp");

	terminate_app = false;
	tbIcon = new fbTaskBarIcon();
	ini = new Ini();
	cp = new CommandParams();
	fth = new FileThread(cp, ini, tbIcon);
	sic = new wxSingleInstanceChecker(sLogo);
	terminate_started = false;

	myOut("MainApp::MainApp out");
}
	
MainApp::~MainApp(void)
{
	myOut("in MainApp::~MainApp");
	Terminate();
	delete fth;
	myOut("thread deleted");
	//delete tbIcon;
	delete cp;
	delete ini;
	delete sic;
	myOut("MainApp::~MainApp out");
}	

void MainApp::OnQueryEndSession(wxCloseEvent &evt)
{
	myOut("in MainApp::OnQueryEndSession");
	Terminate();
	closeMainFrame();
	myOut("MainApp::OnQueryEndSession out");
}	

bool MainApp::OnInit()
{
	Log::Info(_T("%s %s"), sLogo.c_str(), sVersion.c_str());

	if( sic->IsAnotherRunning() )
		return FALSE;
	
	icon1 = new wxIcon(_T("startingIcon"), wxBITMAP_TYPE_ICO_RESOURCE );
	icon2 = new wxIcon(_T("backupIcon"), wxBITMAP_TYPE_ICO_RESOURCE );
	tbIcon->SetIcon(*icon1, sLogo);

	wxCmdLineParser *parser = new wxCmdLineParser(argc, argv);
	try
	{
#ifndef FINAL
		sLogo << _T(" DEBUG version ");
#endif
		wxString str(_T("\ncommand line:\n"));
		for(int i=0; i < argc; i++)
		{
			str << argv[i] << _T(" ");
		}
		
		wxString parserLogo;
		parserLogo.Printf(_T("%s %s %s"), sLogo.c_str().AsWChar(), sVersion.c_str().AsWChar(), str.c_str().AsWChar());
		parser->SetLogo(parserLogo);
		parser->DisableLongOptions();
		parser->SetDesc(cmdLineDesc);

		//OnInitCmdLine(*parser);
		// parsing
		switch( parser->Parse(true) )
		{
			case -1:
			case 1:
				throw Exception(_T("ttt"));
		}
		parser->Found(Ini::logstr, &cp->sLogName);
		cp->sLogName.Trim();
		cp->bLAppend = cp->sLogName.Length() > 0 && parser->Found(Ini::lappend);
		if( cp->sLogName.Length() > 0 )
		{
			wxString fileName (cp->sLogName);
			wxFile::OpenMode mode = cp->bLAppend ? wxFile::OpenMode::write_append : wxFile::OpenMode::read_write;

			//wxFile *file = new wxFile();
			//file->Open(fileName, mode);
			//file->Write(_T("test"));

			//delete file;

			Log::Initialize(fileName, mode);
		}
		Log::Info(parserLogo);
		Log::Info(_T("starting to parse options"));

		checkOneOrOther(parser, Ini::src, Ini::srcl, true);
		checkOneOrOther(parser, Ini::ifiles, Ini::ifilesl, true);
		checkOneOrOther(parser, Ini::efiles, Ini::efilesl, false);
		checkOneOrOther(parser, Ini::edirs, Ini::edirsl, false);

		ini->readFiles(parser);
		
		parser->Found(Ini::dst, &cp->sDestination);
		cp->sDestination.Trim();
		if( parser->Found(Ini::src, &cp->sSource) )
		{
			ini->sl->Add(cp->sSource);
		}
		parser->Found(Ini::sync, &cp->sSync);
		cp->sSync.Trim();
		
		cp->bIFPath = parser->Found(Ini::ifpath);
		cp->bDelete = parser->Found(Ini::dele);
		cp->bVerbose = parser->Found(Ini::verbose);

		if( parser->Found(Ini::ifiles, ini->sif) )
		{
			ini->sif->Trim();
			ini->ifl->Add(*ini->sif);
		}
		*ini->sif = ini->conIFiles();
		if( ini->sif->Length() > 0 )
		{
			//wxRE_ICASE works only in wxRE_EXTENDED syntax
			if( !cp->reIFiles.Compile(*ini->sif, wxRE_EXTENDED | wxRE_NOSUB | wxRE_ICASE) )
			{
				throw Exception(_T("\nwrong regular expression \'")+*ini->sif+_T("\'"));
			}
		}
		else
			throw Exception(_T("Empty include file list."));

		if( parser->Found(Ini::edirs, ini->sed) )
		{
			ini->sed->Trim();
			ini->edl->Add(*ini->sed);
		}
		*ini->sed = ini->conEDirs();
		if( ini->sed->Length() > 0 )
		{
			//wxRE_ICASE works only in wxRE_EXTENDED syntax
			if( !cp->reEDirs.Compile(*ini->sed, wxRE_EXTENDED | wxRE_NOSUB | wxRE_ICASE) )
			{
				throw Exception(_T("\nwrong regular expression \'")+*ini->sed+_T("\'"));
			}
		}

		if( parser->Found(Ini::efiles, ini->sef) )
		{
			ini->sef->Trim();
			ini->efl->Add(*ini->sef);
		}
		*ini->sef = ini->conEFiles();
		Log::Info(_T("ef \'%s\'"), ini->sef->c_str());
		if( ini->sef->Length() > 0 )
		{
			//wxRE_ICASE works only in wxRE_EXTENDED syntax
			if( !cp->reEFiles.Compile(*ini->sef, wxRE_EXTENDED | wxRE_NOSUB | wxRE_ICASE) )
			{
				throw Exception(_T("\nwrong regular expression \'")+*ini->sef+_T("\'"));
			}
		}

		if( cp->sSync != Ini::backup && cp->sSync != Ini::restore )
		{
			throw Exception(_T("Wrong operation type \'"+cp->sSync+_T("\'! Only 'backup', 'restore' allowed")));
		}
		if( cp->bVerbose )
			Log::Info(_T("verbose"));

		delete parser;
		parser = NULL;
	}
	catch(Exception &ex)
	{
		delete parser;

		Log::Info(_T("%s Wrong command line\n%s"), err_str, ex.sMessage.c_str().AsWChar());
		return FALSE;
	}

	wxPrintf(_T("%s %s"), sLogo.c_str(), sVersion.c_str());
	fMain = new MainFrame(sLogo);
	fMain->SetIcon(*icon2);

	fMain->Show(FALSE);
	
	frame_closed = false;
	SetTopWindow(fMain);

	return fth->Create() == wxTHREAD_NO_ERROR && fth->Run() == wxTHREAD_NO_ERROR;
}

void MainApp::closeMainFrame(void)
{
	csFrame.Enter();
	myOut("in closeMainFrame");
	if( !frame_closed && !terminate_started )
	{
		if( fth->myIsActive() )
		{
			myOut("closeMainFrame waiting while thread terminates");
			fth->Wait();
			myOut("closeMainFrame thread terminated");
		}
		{
			frame_closed = true;
			wxPostEvent(fMain, wxCloseEvent(wxEVT_CLOSE_WINDOW));
			this->Exit();
			myOut("closeMainFrame close event posted");
		}
	}
	myOut("closeMainFrame out");
	csFrame.Leave();
}

void MainApp::Terminate(void)
{
	myOut("in Terminate");
	if( !terminate_app )
	{
		myOut("Terminate testing is thread alive");
		if( fth->myIsActive() )
		{
			terminate_started = true;
			myOut("Terminate terminating thread");
			fth->Delete();
			myOut("Terminate waiting while thread teminates");
			fth->Wait();
			myOut("Terminate thread terminated");
			terminate_started = false;
		}
		terminate_app = true;
	}
	else
		closeMainFrame();
	myOut("out Terminate");
}

/* MainFrame */

BEGIN_EVENT_TABLE(MainFrame, wxFrame)
	EVT_CLOSE(MainFrame::onClose)
END_EVENT_TABLE()

MainFrame::MainFrame(const wxString& title)
       : wxFrame(NULL, -1, title, wxDefaultPosition, wxSize(200, 100), wxDEFAULT_FRAME_STYLE)
{
	wxString message;
	message.sprintf(_T("Please wait while %s stop working."), sLogo.c_str().AsWChar());
	wxStaticText *st = new wxStaticText(this, -1, message, wxPoint(0,50), wxSize(200, 20) , wxALIGN_CENTRE);
}

void MainFrame::onClose(wxCloseEvent &event)
{
	myOut("in MainFrame::onClose");
	Destroy();
	myOut("MainFrame::onClose out");
}

/* fbTaskBarIcon */

enum {
    PU_EXIT = 10001,
		PU_RESTORE,
		PU_HIDE,
		PU_PAUSE,
		PU_RESUME
};

BEGIN_EVENT_TABLE(fbTaskBarIcon, wxTaskBarIcon)
	EVT_MENU(PU_EXIT,    fbTaskBarIcon::onMenuExit)
END_EVENT_TABLE()

fbTaskBarIcon::fbTaskBarIcon()
	: wxTaskBarIcon()
{
}

void fbTaskBarIcon::onMenuExit(wxCommandEvent& )
{
	myOut("in fbTaskBarIcon::onMenuExit");
	if( wxGetApp().terminate_app )
		wxGetApp().closeMainFrame();
	else
		wxGetApp().Terminate();
	myOut("leaving fbTaskBarIcon::onMenuExit");
}

void fbTaskBarIcon::OnRButtonUp(wxEvent&)
{
	myOut("in OnRButtonUp");
	wxMenu      menu;
	menu.Append(PU_EXIT, wxGetApp().terminate_app ? _T("E&xit") : _T("&Stop"));
	PopupMenu(&menu);
	myOut("leaving OnRButtonUp");
}

